'UDP request-response client.'
# Uses blocking sockets - however, I can't think of a situation where running
# several of these in parallel would be useful. (This is meant to be used
# primarily by nodes, not the master server.)
#
# I guess pinging multiple nodes simultaneously could be neat... But with since
# the nodes send updates to the master at most every ~30 minutes, why hurry?
#
# Besides, I'd have to make ping/SSH checks non-blocking too, which would raise
# the question of why I was re-writing the Twisted framework from the ground up?
#
# If the peers were linked in some kind of P2P network... All the layouts I 
# considered had neighbourhoods far too small to be worth making non-blocking,
# though. Especially considering how cheap Python threads are.
#
# When I get around to passing tasks from the master server, I'll probably make
# that non-blocking.
import socket
import net
import traceback
import struct

BUFFSIZE=4096

def connect(hostname):
    'Returns a socket on success, raises a socket.error on failure.'
    #try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    s.settimeout(net.timeout)
    s.connect((hostname,net.port))   
    return s
    #except:
        #print 'Failed to connect to',hostname
        #traceback.print_exc()
        #raise socket.error('connect failed')

def ask(sock, request):
    'Returns a response on success, raises a socket.error on failure.'
    # prefix with length for TCP transit
    request = struct.pack('!H', len(request)+2) + request
    # send request
    sent = sock.send(request)
    while sent<len(request):
        request=request[sent:]
        sent = sock.send(request)
    # receive response according to protocol definition in server.py
    acc = ''
    while 1:
        chunk = sock.recv(BUFFSIZE)
        if not chunk:
            # check for 0-bytes-received disconnect
            raise socket.error('response not received when asked')
        acc += chunk
        # NOTE: this function would be buggy if sending+receiving ever
        # was asynchronous, due to accumulator losses on over-receives
        # Luckily, a single-threaded request-response model prevents
        # that from happening.

        # check if an entire response has been accumulated
        if len(acc)>2:
            size = struct.unpack('!H',acc[:2])[0]
            # pop it off and return it, if so
            if len(acc)>=size:
                response,acc = acc[:size],acc[size:]
                return response[2:]

def close(sock):
    'Probably optional, what with garbage collection and whatnot.'
    # useful in some situations though
    sock.shutdown(socket.SHUT_RDWR)
    sock.close()

if __name__=='__main__':
    import protocol.node
    s=connect('127.0.1.1')
    print ask(s, protocol.node.ping())
    close(s)
